package ferp.center.server;

import com.google.appengine.api.backends.BackendServiceFactory;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskHandle;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.gson2.Gson;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* User: igorgok
* Date: 5/22/12 9:56 AM
*/
public class Action
{
  // frontend address base
  public static final String BASE = "ferpcenter.appspot.com";

  public static abstract class Read<Input, Output>
  {
    protected abstract Output run(EntityManager em, Input input) throws IOException;
  }

  public static abstract class Write<Input, Output>
  {
    protected abstract Output run(EntityManager em, Input input) throws IOException;
  }

  public static abstract class Task
  {
    protected abstract void run(EntityManager em, HttpServletRequest request) throws IOException;
  }

  public static <Input, Output> void execute(HttpServletRequest request, HttpServletResponse response, Class<Input> ic, Read<Input, Output> action) throws IOException
  {
    Output result = null;
    EntityManager em = EMF.ferpcenter().createEntityManager();

    try
    {
      response.setContentType("text/json");
      response.setCharacterEncoding(UTF8);

      PrintWriter pw = response.getWriter();
      // cannot use request.getReader() in case of reading parameters before getting here:
      // java.lang.IllegalStateException: STREAMED
      // therefore, using request.getInputStream instead
      Input input = gson.fromJson(new InputStreamReader(request.getInputStream(), UTF8), ic);

      // log input
      input(input);

      try
      {
        result = action.run(em, input);
      }
      catch(Exception e)
      {
        logger.log(Level.SEVERE, "read action problem", e);
      }

      String json = gson.toJson(result);

      // log output
      output(json);

      pw.println(json);
    }
    finally
    {
      em.close();
    }
  }

  public static <Input, Output> void execute(HttpServletRequest request, HttpServletResponse response, Class<Input> ic, Write<Input, Output> action) throws IOException
  {
    Output result = null;
    EntityManager em = EMF.ferpcenter().createEntityManager();
    EntityTransaction tx = em.getTransaction();

    try
    {
      response.setContentType("text/json");
      response.setCharacterEncoding(UTF8);

      PrintWriter pw = response.getWriter();
      // cannot use request.getReader() in case of reading parameters before getting here:
      // java.lang.IllegalStateException: STREAMED
      // therefore, using request.getInputStream instead
      Input input = gson.fromJson(new InputStreamReader(request.getInputStream(), UTF8), ic);

      // log input
      input(input);

      try
      {
        // start the XG transaction (no more then 5 entity groups)
        // the following line should be added to the "persistense.xml":
        //   <property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true"/>
        tx.begin();

        result = action.run(em, input);

        tx.commit();
      }
      catch(Exception e)
      {
        logger.log(Level.SEVERE, "write action problem, trying to rollback", e);

        if (tx.isActive())
          tx.rollback();
      }

      String json = gson.toJson(result);

      // log output
      output(json);

      pw.println(json);
    }
    finally
    {
      em.close();
    }
  }

  public static void execute(HttpServletRequest request, Task task) throws IOException
  {
    EntityManager em = EMF.ferpcenter().createEntityManager();

    try
    {
      task.run(em, request);
    }
    finally
    {
      em.close();
    }
  }

  public static void execute(HttpServletRequest request, Task task, int attempts) throws IOException
  {
    EntityManager em = EMF.ferpcenter().createEntityManager();

    try
    {
      for (int i = 0; i < attempts; ++i)
      {
        EntityTransaction tx = em.getTransaction();

        tx.begin();

        try
        {
          task.run(em, request);

          tx.commit();

          break;
        }
        catch (Exception e)
        {
          logger().log(Level.WARNING, "task attempt " + i + " failed", e);

          if (tx.isActive())
            tx.rollback();

          if (i < attempts - 1)
            // sleep before next attempt
            try
            {
              Thread.sleep(5 * 1000);
            }
            catch (InterruptedException ie)
            {
              /* do nothing */
            }
        }
      }
    }
    finally
    {
      em.close();
    }
  }

  public interface AppendTaskOptions
  {
    void execute(TaskOptions to);
  }

  public static void queueTaskForBackend(HttpServletResponse response, String purpose, String address) throws IOException
  {
    queueTaskForBackend(response, purpose, address, null);
  }

  public static void queueTaskForBackend(HttpServletResponse response, String purpose, String address, AppendTaskOptions append) throws IOException
  {
    // create "train bid NN" task and push it into the default task queue
    Queue queue = QueueFactory.getDefaultQueue();
    // specify backend URL with method POST so that the "doPost" method of this servlet will be called
    // in a context of the backend allowing it to run for a long time
    TaskOptions to = TaskOptions.Builder
        .withUrl(address)
        .header("Host", BackendServiceFactory.getBackendService().getBackendAddress(BACKEND_NAME))
        .method(TaskOptions.Method.POST);

    if (append != null)
      append.execute(to);

    TaskHandle h = queue.add(to);

    response.setContentType("text/plain");
    response.setCharacterEncoding(UTF8);

    PrintWriter pw = response.getWriter();

    pw.println("queued " + purpose + " task " + h);
  }

  public static Gson gson() {return gson;}
  public static Logger logger() {return logger;}

  // UTF-8 character set name
  private static final String UTF8 = "UTF-8";

  // FERP center backend name
  private static final String BACKEND_NAME = "training";

  // GSON instance
  private static final Gson gson = new Gson();
  private static final Logger logger = Logger.getLogger(Action.class.getName());

  private static void input(Object input) {logger.info(gson.toJson(input));}
  private static void output(String json)
  {
    logger.info(json.length() < 1000 ? json : (json.substring(0, 100) + "... " + (json.length() - 1000) + " more bytes"));
    //logger.info(json);
  }
}
